<!DOCTYPE html>
<html>
<head>
  <title>Mapbox GL JS debug page</title>
  <meta charset='utf-8'>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <script src='./lib/mapbox-gl-dev.js'></script>
  <link href='./lib/mapbox-gl.css' rel='stylesheet' />
  <script src="./lib/resize.js"></script>
  <script src="./lib/turf.js"></script>
  <script src="./lib/fetch.js"></script>

  <style>
   body { margin: 0; padding: 0; }
   html, body, #map { height: 100%; }
   canvas { position: absolute; left:0; top: 0; z-index: 10000; }
  </style>
</head>

<body>
  <div id='map'>
    <canvas id="canvas" width="400" height="400" style="display:none;"></canvas>
  </div>

  <script>
   'use strict';

   // -----
   // Utility functions
   function RGBtoHSV(r, g, b) {
       if (arguments.length === 1) {
           g = r.g, b = r.b, r = r.r;
       }
       let max = Math.max(r, g, b), min = Math.min(r, g, b),
           d = max - min,
           h,
           s = (max === 0 ? 0 : d / max),
           v = max / 255;

       switch (max) {
           case min: h = 0; break;
           case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break;
           case g: h = (b - r) + d * 2; h /= 6 * d; break;
           case b: h = (r - g) + d * 4; h /= 6 * d; break;
       }

       return { h: h, s: s, v: v};
   }

   function HSVtoRGB(h, s, v) {
       var r, g, b, i, f, p, q, t;
       if (arguments.length === 1) {
           s = h.s, v = h.v, h = h.h;
       }
       i = Math.floor(h * 6);
       f = h * 6 - i;
       p = v * (1 - s);
       q = v * (1 - f * s);
       t = v * (1 - (1 - f) * s);
       switch (i % 6) {
           case 0: r = v, g = t, b = p; break;
           case 1: r = q, g = v, b = p; break;
           case 2: r = p, g = v, b = t; break;
           case 3: r = p, g = q, b = v; break;
           case 4: r = t, g = p, b = v; break;
           case 5: r = v, g = p, b = q; break;
       }
       return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
   }

   function HEXtoRGB (hex) {
       var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
       return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16)} : null
   }

   function RGBtoHEX (r, g, b) {
       if (typeof r === 'object') {
           const args = r
           r = args.r; g = args.g; b = args.b;
       }
       r = Math.round(r).toString(16)
       g = Math.round(g).toString(16)
       b = Math.round(b).toString(16)

       r = r.length === 1 ? '0' + r : r
       g = g.length === 1 ? '0' + g : g
       b = b.length === 1 ? '0' + b : b

       return '#' + r + g + b
   }

   // -----
   // Canvas resizing and 2D context
   let map_canvas = document.getElementById('canvas');
   const resizeCanvas = () => {
       let parent = map_canvas.parentNode, styles = getComputedStyle(parent),
           width = parseInt(styles.getPropertyValue("width"), 10),
           height = parseInt(styles.getPropertyValue("height"), 10);
       map_canvas.width = width;
       map_canvas.height = height;
   };
   addResizeListener(document.getElementById('map'), resizeCanvas);
   resizeCanvas();

   let context = map_canvas.getContext("2d");


   // -----
   // the following canvas example is copied from http://www.html5canvastutorials.com/tutorials/html5-canvas-exploding-dots/
   let points = [], numPoints = 100, i, gravity = 0.1;

   function initPoint(p) {
       p.x = map_canvas.width / 2;
       p.y = 0;
       p.vx = Math.random() * 6 - 3;
       p.vy = Math.random() * 10;
       p.radius = Math.random() * 4 + 1;
   }

   function updateDots() {
       var i, point, len = points.length;
       for(i = 0; i < len; i += 1) {
           point = points[i];
           point.vy -= gravity;
           point.x += point.vx;
           point.y += point.vy;
           if(point.x > map_canvas.width ||
              point.x < 0 ||
              point.y > map_canvas.height ||
              point.y < 0) {
               initPoint(point);
           }
       }
       if(len < numPoints) {
           point = {};
           initPoint(point);
           points.push(point);
       }
   }

   function drawDots() {
       var i, point, len = points.length;
       for(i = 0; i < len; i += 1) {
           point = points[i];
           context.beginPath();
           context.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false);
           context.fillStyle = "#f00";
           context.fill();

       }
   }

   // -----
   // Lighthouses animation
   let lighthouses = null, visible_lighthouses = [];

   let getXY = point => {
       const bounds = map.getBounds();
       return [ map_canvas.width * (point[0] - bounds.getWest()) / (bounds.getEast() - bounds.getWest()),
                map_canvas.height * (point[1] - bounds.getSouth()) / (bounds.getNorth() - bounds.getSouth())];
   };

   let getDistance = distance => {
       const bounds = map.getBounds();
       return map_canvas.width * distance / (bounds.getNorth() - bounds.getSouth());
   };

   const renderingColors = { 'white': '#ffff00', 'red': '#ff0000', 'green': '#00ff00', 'blue': '#0000ff', 'yellow': '#ffff00',
                             'amber': '#ffbf00', 'violet': '#ee82ee',  'orange': '#ffa500', 'magenta': '#ff00ff'};

   let getStep = (time, sequence) => {
       let patt = /([0-9]+(\.[0-9]*)?|\.[0-9]+)/igm, t = 0., step = 0, match;
       while (match = patt.exec(sequence)) {
           /* if (step % 2 === 1 && (patt.lastIndex >= sequence.length || sequence[patt.lastIndex] !== ')')) {
            *     console.error('bad sequence string' + sequence);
            *     return null;
            * }*/
           const phase_duration = Number(match[0]);
           if (t <= time && time < t + phase_duration)
               return step % 2 === 1 ? null : step / 2;
           step += 1;
           t += phase_duration;
       }
       return null;
   };

   let getColor = (timestamp, prop, prefix) => {
       const character = prop[prefix + 'character'];
       const color = prop[prefix + 'colour'];

       // http://wiki.openstreetmap.org/wiki/Seamarks/Lights
       // http://wiki.openstreetmap.org/wiki/Seamarks/Light_Characters
       // https://en.wikipedia.org/wiki/Light_characteristic
       if (character === 'Al' || character === 'alternating') {
           // Alternating Light
           const colors = color.split(';'),
                 period = prop.hasOwnProperty(prefix + 'period') ? prop[prefix + 'period'] : 2,
                 step = Math.floor((colors.length * (timestamp % period) / period));
           return renderingColors[colors[step]];
       } else if (character === 'Al.Fl') {
       } else if (character === 'Al.Iso') {
       } else if (character === 'Al.LFl') {
       } else if (character === 'Al.Oc') {
       } else if (character === 'F') {
           // Fixed Light
           return renderingColors[color];
       } else if (character === 'FFl') {
           // Fixed and flashing
           const period = prop.hasOwnProperty(prefix + 'period') ? prop[prefix + 'period'] : 20;
           if ((timestamp % period ) < (period / 2))
               return renderingColors[color];
           let hsv = RGBtoHSV(HEXtoRGB(renderingColors[color]));
           hsv.s = 0.5 * hsv.s;
           return RGBtoHEX(HSVtoRGB(hsv));
       } else if (character === 'Fl' || character === 'jFl') {
           // Flashing Light
           if (prop.hasOwnProperty(prefix + 'sequence')) {
               const step = getStep(timestamp % prop[prefix + 'period'], prop[prefix + 'sequence']);
               return step === null ? null : renderingColors[color];
           } else {
               return timestamp % prop[prefix + 'period'] < 1 ? renderingColors[color] : null;
           }
       } else if (character === 'IQ') {
           const period = prop.hasOwnProperty(prefix + 'period') ? prop[prefix + 'period'] : 15,
                 period_fast = 1., // here must be at least 8 flashed and at least 3 seconds blackout
                 duration = period - 4;
           return (timestamp % period ) < duration && (timestamp % period_fast) < (period_fast / 2) ? renderingColors[color] : null;
       } else if (character === 'Iso' || character === 'ISO') {
           // Isophased Light
           const period = prop.hasOwnProperty(prefix + 'period') ? prop[prefix + 'period'] : 2;
           return (timestamp % period ) < (period / 2) ? renderingColors[color] : null;
       } else if (character === 'IVQ') {
           const period = prop.hasOwnProperty(prefix + 'period') ? prop[prefix + 'period'] : 10,
                 period_fast = 0.1 * period,
                 duration = 0.75 * period;
           return (timestamp % period ) < duration && (timestamp % period_fast) < (period_fast / 2) ? renderingColors[color] : null;
       } else if (character === 'LFl' || character === 'LFl' || character === 'long_flashing') {
           // Long Flash Light
           const period = prop.hasOwnProperty(prefix + 'period') ? prop[prefix + 'period'] : 15,
                 group = prop.hasOwnProperty(prefix + 'group') ? prop[prefix + 'group'] : period / 4;
           return (timestamp % period ) < group ? renderingColors[color] : null;
       } else if (character === 'Mo') {
           const step = getStep(timestamp % prop[prefix + 'period'], prop[prefix + 'sequence']);
           return step === null ? null : renderingColors[color];
       } else if (character === 'Oc' || character === 'rear' /* data bug in 854975740 */) {
           // Occulting Light
           const step = getStep(timestamp % prop[prefix + 'period'], prop[prefix + 'sequence']);
           return step === null ? null : renderingColors[color];
       } else if (character === 'Q') {
           // Quick Flashing Light
           return timestamp % 1 < 0.5 ? renderingColors[color] : null;
       } else if (character === 'Q+LFl') {
           return 'black';
       } else if (character === 'VQ') {
           return (timestamp % 0.75 ) < 0.375 ? renderingColors[color] : null;
       }

       return renderingColors[color];
   };


   function drawLightArc(timestamp, prop, center, prefix) {
       const color = getColor(timestamp / 1000, prop, prefix);

       if (color !== null) {
           let sector_start = prop.hasOwnProperty(prefix + 'orientation') ? prop[prefix + 'orientation'] - 3 :
                              prop.hasOwnProperty(prefix + 'sector_start') ? prop[prefix + 'sector_start'] :
                              0,
               sector_end = prop.hasOwnProperty(prefix + 'orientation') ? prop[prefix + 'orientation'] + 3 :
                            prop.hasOwnProperty(prefix + 'sector_end') ? prop[prefix + 'sector_end'] :
                            359,
               radius = getDistance(prop.hasOwnProperty(prefix + 'radius') ? prop[prefix + 'radius'] / 60. :
                                    prop.hasOwnProperty(prefix + 'range') ? prop[prefix + 'range'] / 600. :
                                    1. / 60);

           sector_start = Math.PI * (270 - sector_start) / 180.;
           sector_end = Math.PI * (270 - sector_end) / 180.;

           context.beginPath();
           context.arc(center[0], center[1], radius, sector_start, sector_end, true);
           context.lineWidth = 7;
           context.strokeStyle = color;
           context.stroke();
       }
   }

   function drawLighthouses(timestamp) {
       for (let i in visible_lighthouses.features) {
           const lighthouse = visible_lighthouses.features[i];
           const prop = lighthouse.properties;
           const center = getXY(lighthouse.geometry.coordinates);

           if (prop.hasOwnProperty('seamark:light:colour')) {
               drawLightArc(timestamp, prop, center, 'seamark:light:');
           } else {
               for (let light = 1, prefix = `seamark:light:${light}:`;
                   prop.hasOwnProperty(prefix + 'colour'); ++light, prefix = `seamark:light:${light}:`) {
                   drawLightArc(timestamp, prop, center, prefix);
               }
           }
       }
   }

   // -----
   // Anmation loop
   let prevTimestamp = null;
   function loop(timestamp) {
       //updateDots();
       if (timestamp - prevTimestamp > 0) {
           context.clearRect(0, 0, map_canvas.width, map_canvas.height);
           //drawDots();
           drawLighthouses(timestamp);
           prevTimestamp = timestamp;
       }
       window.requestAnimationFrame(loop);
   }

   var mapStyle = {
       'version': 8,
       'name': 'Dark',
       'sources': {
           'mapbox': {
               'type': 'vector',
               'url': 'mapbox://mapbox.mapbox-streets-v6'
           },
           'lighthouses': {
               'type': 'geojson',
               //'data': './data/lighthouses_kiel.geojson'
               'data': './data/lighthouses.geojson'
           },
           "canvas": {
               "type": 'canvas',
               "canvas": 'canvas',
               "coordinates": [[0, 0], [0, 1], [1, 1], [1, 0]]
           }
       },
       //'sprite': 'http://localhost:7777/assets/INT1',
       'sprite': 'http://oxidase.github.io/maps/lighthouses/assets/INT1',
       'glyphs': 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
       'layers': [
           {
               'id': 'background',
               'type': 'background',
               'paint': {'background-color': 'hsl(55, 11%, 96%)'}
           },
           {
               'id': 'water',
               'source': 'mapbox',
               'source-layer': 'water',
               'type': 'fill',
               'paint': {'fill-color': 'hsl(185, 9%, 81%)'}
           },
           {
               'id': 'boundaries',
               'source': 'mapbox',
               'source-layer': 'admin',
               'type': 'line',
               'paint': {'line-color': '#797979', 'line-dasharray': [2, 2, 6, 2]},
               'filter': ['all', ['==', 'maritime', 0]]
           },
           {
               'id': 'naval_boundaries',
               'source': 'mapbox',
               'source-layer': 'admin',
               'type': 'line',
               'paint': {'line-color': '#b92dce', 'line-width': 3, 'line-opacity': 0.4},
               'filter': ['all', ['==', 'maritime', 1]]
           },
           {
               'id': 'other_seamarks',
               'source': 'lighthouses',
               'type': 'circle',
               'paint': {'circle-radius': 3, 'circle-color': '#8B88B6'},
               'filter': ['!in', 'seamark:type', 'light_major', 'light_minor', 'landmark']
           },
           {
               'id': 'lighthouses',
               'source': 'lighthouses',
               'type': 'symbol',
               'layout': {
                   'icon-image': '{seamark:type}',
                   'text-field': '{name}',
                   'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
                   'text-offset': [0, -1.2],
                   'text-anchor': 'bottom'
               },
               // https://www.mapbox.com/mapbox-gl-js/style-spec/#types-filter
               'filter': ['in', 'seamark:type', 'light_major', 'light_minor', 'landmark']
           },
           {
               'id': 'canvas',
               'source': 'canvas',
               'type': 'raster',
               'paint': {'raster-opacity': 0.85},
               'minzoom': 5,
               'maxzoom': 24
           },
       ]
   };

   mapboxgl.accessToken = 'pk.eyJ1IjoibGJ1ZCIsImEiOiJCVTZFMlRRIn0.0ZQ4d9-WZrekVy7ML89P4A';
   var map = new mapboxgl.Map({
       container: 'map',
       minZoom: 0,
       style: mapStyle,
       hash: false,
       //center: [10.2736833, 54.4996167],
       //center: [8.4437483, 55.0494226],
       //center: [-1.0890453, 50.7783104],
       //center: [8.5662692, 54.6807713],
       //center: [10.0362728, 54.6714421],
       center: [11.3269337, 58.2985000],
       center: [5.5542500, 58.6580167],
       center: [11.7215945, 57.6535536],
       center: [-2.0318283, 48.6421122],
       center: [0.2143400, 49.4244945],
       center: [10, 52],
       zoom: 5
   });

   const updateCanvas = () => {
       const bounds = map.getBounds();
       map.getSource('canvas').setCoordinates([bounds.getSouthWest(), bounds.getSouthEast(), bounds.getNorthEast(), bounds.getNorthWest()]);

       if (lighthouses !== null) {

           const polygon = {
               type: 'FeatureCollection',
               features: [ turf.polygon([
                   [bounds.getSouthWest().toArray(),
                    bounds.getSouthEast().toArray(),
                    bounds.getNorthEast().toArray(),
                    bounds.getNorthWest().toArray(),
                    bounds.getSouthWest().toArray()]
               ]) ] };

           visible_lighthouses = turf.within(lighthouses, polygon);
       }
   };


   map.on('load', function() {

       let source = map.getSource('lighthouses');
       fetch(source.serialize().data)
           .then(response => response.json())
           .then(json => lighthouses = json)
           .then(updateCanvas)
           .catch(e => console.error(e));

       map.on('resize', resizeCanvas);
       map.on('move', updateCanvas);
       updateCanvas();

       // initialize animation
       loop(performance.now());
   });
  </script>

</body>
</html>
